"""The main function for manual collector"""

import os
import re
import time
import Tkinter as TK
import tkFont
import psutil
import tkMessageBox
import tkFileDialog
import threading
import logging

import crnc
import mcrnc_ada
import tester
import xml_tool
import config
import recorder

from crnc import CRNC_UNIT

__author__  = "Limbo <linbo.wu@aricent.com>"
__status__  = "production"
__version__ = "2.0.5"
__date__    = "17 September 2014"


LOG_PATH = "D:\\_Logs\\ManualCollector"
ICO = "TTCN.ico"


class ManualCollector(object):
    def __init__(self, root):
        self.root = root
        self.__init_self()
        self.setwindow()
        self.set_default_config()
        self.create_menu_bar()
        self.createwidget()
        self.load_default()

        self.check_font()

        self.flash_status()

        monitor = threading.Thread(target=self.monitor)
        monitor.setDaemon(True)
        monitor.start()

        dog = threading.Thread(target=self.watchdog)
        dog.setDaemon(True)
        dog.start()


    def __init_self(self):
        '''init all used value'''
        self.info = None
        self.rnc = None
        self.tester = None
        self.listcfg = config.RncListConfig()
        self.defcfg = config.DefaultConfig()
        self.widgets = {}

        # when click start catch logs, some buttons to blind
        self.blinds = {}
        # { [catch_emil_widge, state], ...}

        self.task = None
        self.task_kwargs = None
        self.singal = threading.Event()
        self.menu_specs = [
            ("options", "_Options"),
            ("setting", "_Setting"),
            ("tools", "_Tools"),
            ("help", "_Help"),
        ]
        self.bunchs = {'last' : None}

    def __init_menu_var(self):
        self.menudict = {}
        self.menu_rnc = {}  # dict for rnc select menu
        self.rnclist = {}   # dict for rnc checkbutton menu value

        self.log_level = TK.IntVar()
        self.comp_log = TK.BooleanVar()
        self.plan_trace = TK.BooleanVar()

    def setwindow(self, w=800, h=700):
        # get screen width and height
        ws = self.root.winfo_screenwidth()
        hs = self.root.winfo_screenheight()
        # calculate position x, y
        x = (ws/2) - (w/2)
        y = (hs/2) - (h/2)
        self.root.geometry("+{0}+{1}".format(x, y))
        self.root.resizable(False, False)
        if os.path.isfile(ICO):
            self.root.iconbitmap(ICO)

    def set_default_config(self):
        default_log_level = self.defcfg.get('loglevel')
        if default_log_level is None or \
            not hasattr(logging, default_log_level.upper()):
            default_log_level = "warning"
            self.defcfg.set('loglevel', default_log_level)

    def create_menu_bar(self):
        self.__init_menu_var()
        mbar = TK.Menu(self.root, tearoff=0)
        for name, label in self.menu_specs:
            underline, label = prepstr(label)
            self.menudict[name] = menu = TK.Menu(mbar, name=name, tearoff=0)
            mbar.add_cascade(label=label, menu=menu, underline=underline)
        self.create_menu_options()
        self.create_menu_help()
        self.root.config(menu=mbar)

    def create_menu_help(self):
        menu = self.menudict['help']
        menu.add_command(label="About", command=self.about_box)

    def create_menu_options(self):
        menu = self.menudict['options']
        menu.add_command(label="Edit info", command=self.show_edit_bunch)
        menu.add_separator()
        for var in config.RNC_TYPE:
            self.menu_rnc[var] = TK.Menu(menu, tearoff=0)
            self.rnclist[var] = {}

        for key in self.menu_rnc.keys():
            self.create_rnc_list(self.menu_rnc[key], key)
            menu.add_cascade(label=key, menu=self.menu_rnc[key])

        menu.add_separator()
        menu.add_command(label="Exit", command=self.root.quit)

    def create_rnc_list(self, menu, rnc_type):
        rnc_list = self.listcfg.get_rnc_list(rnc_type)
        for var in rnc_list:
            self.rnclist[rnc_type][var] = TK.IntVar()
            menu.add_checkbutton(
                label=var, variable=self.rnclist[rnc_type][var],
                command=lambda var=var: self.select_rnc(var)
            )
        menu.add_separator()
        add_rnc_text = 'New ' + rnc_type
        menu.add_command(label=add_rnc_text,
                         command=lambda: self.show_add_bunch(rnc_type))

    def update_rnc_list(self, rnc_type):
        self.menu_rnc[rnc_type].delete(0, 'end')
        self.rnclist[rnc_type].clear()
        self.create_rnc_list(self.menu_rnc[rnc_type], rnc_type)

    def update_menu_setting(self):
        menu = self.menudict['setting']
        menu.delete(0, 'end')
        log_menu = TK.Menu(menu, tearoff=0)
        menu.add_cascade(label='Set log level', menu=log_menu)
        for var in ['debug', 'info', 'warning', 'error']:
            log_menu.add_radiobutton(
                label=var.capitalize(), 
                variable=self.log_level, 
                value=getattr(logging, var.upper()), 
                command=lambda var=var: self.update_log_level(var)
            )
        self.log_level.set(getattr(logging, self.defcfg.get('loglevel').upper()))

        if not self.info:
            return

        menu.add_separator()
        emil = TK.Menu(menu, tearoff=0)
        menu.add_cascade(label='Set Emil Units', menu=emil)
        if self.info.type == "cRNC":
            rnc_units = CRNC_UNIT
        elif self.info.type == "ADA":
            rnc_units = mcrnc_ada.ADA_UNIT
        elif self.info.type == "mcRNC":
            rnc_units = mcrnc_ada.MC_UNIT
        units = {}
        for var in rnc_units:
            units[var] = TK.IntVar()
        units[rnc_units[0]].set(1)
        for var in rnc_units:
            emil.add_checkbutton(label=var, variable=units[var], 
                                 command=lambda:self.set_emil_unit(units))
        
        if self.info.type == "cRNC":
            menu.add_checkbutton(label='Enable Computer Log', variable=self.comp_log,
                                 command=lambda:self.rnc.set_comp_log(self.comp_log.get()))
            self.comp_log.set(False)
            menu.add_checkbutton(label='Enable Plan Trace', variable=self.plan_trace)
            self.plan_trace.set(True)

    def update_menu_tools(self):
        menu = self.menudict['tools']
        menu.delete(0, 'end')
        if not self.info:
            return
        check = TK.Menu(menu, tearoff=0)
        menu.add_cascade(label='Check Info', menu=check)
        check.add_command(
            label=self.info.type, 
            command=lambda:self.set_task(self.task_check, unit="rnc")
        )
        check.add_command(
            label="Tester ets", 
            command=lambda:self.set_task(self.task_check, unit="ets")
        )
        check.add_command(
            label="Tester Wireshark", 
            command=lambda:self.set_task(self.task_check, unit="wireshark")
        )
        check.add_separator()
        check.add_command(
            label="Disconnect all", 
            command=lambda:self.set_task(self.task_disconnect, check_lock=True)
        )
        contol_menu = TK.Menu(menu, tearoff=0)
        menu.add_cascade(label='Remote control', menu=contol_menu)
        contol_menu.add_command(
            label="Restart %s" % self.info.type, 
            command=lambda:self.set_task_pre("restart", "rnc")
        )
        contol_menu.add_command(
            label="Restart Tester", 
            command=lambda:self.set_task_pre("restart", "tester")
        )
        contol_menu.add_separator()
        contol_menu.add_command(
            label="Start TAD", 
            command=lambda:self.set_task(self.tester.tad, cmd="start", check_lock=True)
        )
        contol_menu.add_command(
            label="Shutdown TAD", 
            command=lambda:self.set_task(self.tester.tad, cmd="shutdown", check_lock=True)
        )
        if self.info.type == "mcRNC" or self.info.type == "ADA":
            menu.add_command(
                label="Reset SS7", 
                command=lambda:self.set_task(self.rnc.reset_ss7, check_lock=True)
            )
        menu.add_separator()
        menu.add_command(
            label="Active Counter", 
            command=lambda:self.set_task(self.rnc.active_counter)
        )
        menu.add_separator()
        menu.add_command(
            label="Format xml", 
            command=lambda:self.set_task_pre("format")
        )

    def update_menu(self):
        self.update_menu_setting()
        self.update_menu_tools()

    def set_rnclist(self, name, value):
        if name:
            rnc_type = name.split('_')[0]
            if rnc_type in self.rnclist and name in self.rnclist[rnc_type]:
                self.rnclist[name.split('_')[0]][name].set(value)

    #def select_rnc(self, name, check=True):
    def select_rnc(self, name, first_select=False):
        """if rnc changed, self.info will be changed too"""
        if first_select:
            last_name = None
        else:
            last_name = self.defcfg.get('rnc')
        if self.is_locked() or not self.is_taskable():
            if last_name != name:
                self.set_rnclist(name, 0)
            self.set_rnclist(last_name, 1)
            return None
        else:
            if last_name != name:
                self.set_rnclist(last_name, 0)
                if not first_select:
                    self.defcfg.set('rnc', name)
                    msg = "Change RNC to " + name
                else:
                    msg = "Load default RNC " + name
                self.info = None
                self.logger.info(msg, '')
                self.rnc_recorder.write_file("\n" + msg + "\n")
                self.tester_recorder.write_file("\n" + msg + "\n")
            self.set_rnclist(name, 1)

        if self.info is None:
            self.info = self.listcfg.get(name)
            self.update_interface()

    def update_log_level(self, level):
        set_logger_level(level)
        self.defcfg.set('loglevel', level)

    def __init_widget_value(self):
        self.font = tkFont.Font(size=9, family='Segoe UI')
        self.objects = [
            [3, 'emil', 'Emil log', 'rnc', ('catch', 'collect')],
            [1, 'counter', 'Counter', 'rnc', ('catch', 'collect')],
            [2, 'wireshark', 'Wireshark', 'tester', ('catch', 'collect')],
            [4, 'ets', 'Tester log', 'tester', ('collect')],
            [5, 'plan', 'RNW Plan', 'rnc', ('collect')]
        ]

        self.widgets['name'] = TK.StringVar()
        self.widgets['detail'] = TK.StringVar()
        self.widgets['case'] = TK.StringVar()
        self.widgets['plan'] = TK.IntVar()

        for handler in ('catch', 'collect'):
            self.widgets[handler] = {}
            for obj in self.objects:
                if handler in obj[-1]:
                    self.widgets[handler][obj[1]] = TK.IntVar()

        self.widgets['catch']['emil'].set(1)
        self.widgets['collect']['emil'].set(1)
        self.widgets['plan'].set(1)
        
        self.status = None
        
        self.editinfo = dict(
            title = TK.StringVar(),
            name = TK.StringVar(),
            type = TK.StringVar(),
            rnc_hostname = TK.StringVar(),
            rnc_username = TK.StringVar(),
            rnc_password = TK.StringVar(),
            tester_hostname = TK.StringVar(),
            tester_username = TK.StringVar(),
            tester_password = TK.StringVar(),
            ets = TK.StringVar()
        )
        
        self.addinfo = dict(
            title = TK.StringVar(),
            name = TK.StringVar(),
            type = TK.StringVar(),
            rnc_hostname = TK.StringVar(),
            rnc_username = TK.StringVar(),
            rnc_password = TK.StringVar(),
            tester_hostname = TK.StringVar(),
            tester_username = TK.StringVar(),
            tester_password = TK.StringVar(),
            ets = TK.StringVar()
        )

    def check_font(self):
        font_file = "C:\\Windows\\Fonts\\segoeui.ttf"
        if not os.path.isfile(font_file):
            self.logger.warn("Font 'Segoe UI' not found. Interface may be not alignment.", '')

    def createwidget(self):
        self.__init_widget_value()

        top_frame = TK.Frame(self.root)
        top_frame.pack(fill='x')

        bunch_frame = TK.Frame(self.root)
        self.createwidget_bunch(bunch_frame)
        bunch_frame.pack(fill='x')

        bottom_frame = TK.Frame(self.root)
        bottom_frame.pack(side='bottom', fill='x')

        top_left_frame = TK.Frame(top_frame)
        top_left_frame.pack(side='left', fill='y')
        # a special blank
        TK.Frame(top_frame).pack(side='right', padx=8)
        top_right_frame = TK.Frame(top_frame)
        top_right_frame.pack(side='right', fill='y')

        self.createwidget_rncinfo(top_left_frame)
        plan = TK.LabelFrame(top_left_frame, text="RNW Plan")
        self.createwidget_plan(plan)
        plan.pack(side='bottom', padx=8, pady=4)

        catch = TK.LabelFrame(top_right_frame, text="Start/Stop catch logs")
        self.createwidget_catch(catch)
        catch.pack(fill='x', pady=4)

        collect = TK.LabelFrame(top_right_frame, text="Collect logs")
        self.createwidget_collect(collect)
        collect.pack(fill='x', pady=4)

        tmp_font = tkFont.Font(size='9',family='Consolas')
        TK.Entry(bottom_frame, font=tmp_font, width=115, state='readonly',
                 relief='flat', textvariable=self.widgets['detail']
            ).pack(padx=2, side='left')

    def flash_status(self, status=[]):
        if len(status) == 0:
            # 0 for off
            # 1 for green
            # 2 for red
            status.append(-1)
            status.append((3, 3, 15, 15))
            status.append(dict(fill='green', outline='gray'))
        if self.singal.isSet():
            if status[0] != 1:
                status[2]['fill'] = 'green'
                self.status.create_oval(*status[1], **status[2])
                status[0] = 1
            elif status[0] != 2:
                status[2]['fill'] = 'red'
                self.status.create_oval(*status[1], **status[2])
                #self.status.create_oval(3, 3, 15, 15, fill='red', outline='gray')
                status[0] = 2
        else:
            if status[0] != 0:
                status[2]['fill'] = 'SystemButtonFace'
                self.status.create_oval(*status[1], **status[2])
                #self.status.create_oval(3, 3, 15, 15, fill="SystemButtonFace", outline='gray')
                self.logger.record("")
                status[0] = 0
        self.status.after(300, self.flash_status)

    def createwidget_rncinfo(self, parent):
        TK.Frame(parent).pack(pady=5)
        status_frame = TK.Frame(parent)
        status_frame.pack(fill='x')
        new_frame = TK.Frame(status_frame)
        new_frame.pack()
        tmp_font = tkFont.Font(size=24,family='Consolas')
        TK.Label(new_frame, font=tmp_font, textvariable=self.widgets['name']).pack(side='left')
        self.status = TK.Canvas(new_frame, width=16, height=16)
        self.status.pack(side='left', fill='y')

    def createwidget_plan(self, parent):
        MODES = [
            ("Active new Plan", 1),
            ("Delele Plan", 2)
        ]

        for text, value in MODES:
            TK.Radiobutton(parent, text=text, value=value, font=self.font,
                           variable=self.widgets['plan']
                ).pack(side='left', padx=3, pady=4)
        TK.Button(parent, text="Action", width=8, font=self.font,
                  command=lambda:self.set_task_pre("plan")
            ).pack(side='right', padx=6, pady=4)


    def createwidget_catch(self, parent):
        for var in self.objects :
            if 'catch' in var[-1]:
                if not self.blinds.has_key(var[1]):
                    self.blinds[var[1]] = []
                self.blinds[var[1]].append(
                    TK.Checkbutton(parent, text=var[2], font=self.font,
                        variable=self.widgets['catch'][var[1]]
                    )
                )
                
                self.blinds[var[1]][0].pack(side='left', padx=3)
                self.blinds[var[1]].append(True)

        TK.Button(parent, text="Stop", width=8, font=self.font,
                  command=lambda:self.set_task(self.task_catch_stop)
            ).pack(side='right', padx=6, pady=4)
        TK.Button(parent, text="Start", width=8, font=self.font,
                      command=lambda:self.set_task(self.task_catch_start)
            ).pack(side='right', padx=6, pady=4)

    def createwidget_collect(self, parent):
        for var in self.objects :
            if 'collect' in var[-1]:
                TK.Checkbutton(parent, text=var[2], font=self.font,
                               variable=self.widgets['collect'][var[1]]
                    ).pack(side='left', padx=3)

        self.blinds['collect'] = []
        self.blinds['collect'].append(
            TK.Button(parent, text="Collect", width=8, font=self.font,
                      command=lambda:self.set_task(self.task_collect)
            )
        )
        self.blinds['collect'][0].pack(side='right', padx=6, pady=4)
        self.blinds['collect'].append(True)

    def createwidget_bunch(self, parent):
        bar_frame = TK.Frame(parent)
        bar_frame.pack(padx=8, fill='x')
        
        is_recorder = self.defcfg.get('recorder') == str(True)
        if not is_recorder:
            self.defcfg.set('recorder', False)

        MODES = (
            ("rnc", "RNC Respond"),
            ("tester", "Tester Respond"),
            ("logger", "Logger Info"),
            ("edit", "Edit RNC Info"),
            ("add", "Add new RNC"),
            ("counter", "cRNC Counter")
        )
        for i in xrange(len(MODES)):
            self.bunchs[MODES[i][0]] = []

        rnc_link_frame, rnc_link_text = self.createwidget_msg_text(parent)
        #self.cmd_record = RecordText(respond_text)
        #self.bunchs['respond'].append(respond_frame)
        self.rnc_recorder = recorder.RecordFile("rnc", is_recorder, rnc_link_text)
        self.bunchs['rnc'].append(rnc_link_frame)

        tester_link_frame, tester_link_text = self.createwidget_msg_text(parent)
        self.tester_recorder = recorder.RecordFile("tester", is_recorder, tester_link_text)
        self.bunchs['tester'].append(tester_link_frame)

        logger_frame, logger_text = self.createwidget_msg_text(parent)
        self.logger = recorder.RecordLogger(logger_text)
        self.bunchs['logger'].append(logger_frame)

        self.bunchs['edit'].append(self.add_editframe(parent, self.editinfo, 'edit'))
        self.bunchs['add'].append(self.add_editframe(parent, self.addinfo, 'add'))
        self.bunchs['counter'].append(self.add_counter_frame(parent))

        for (key, text) in MODES:
            self.bunchs[key].append(
                TK.Button(bar_frame, text=text, fg='#a9a9a9',
                          relief='flat', disabledforeground='#000',
                          command=lambda key=key : self.click_bunch(key))
            )
            if key == 'rnc':
                self.bunchs[key][-1].pack(side='left')
            elif key == 'tester' or key == 'logger':
                self.bunchs[key].append(
                    self.createwidget_separator(bar_frame, 'left')
                )
                self.bunchs[key][-2].pack(side='left')
            else:
                self.bunchs[key].append(
                    self.createwidget_separator(bar_frame, 'left', False)
                )

        TK.Frame(bar_frame).pack(side='right', padx=4) # a special blank
        TK.Entry(bar_frame, width=24, bg="#FFFFFF", textvariable=self.widgets['case']
            ).pack(side='right')
        TK.Label(bar_frame, text="Case Name :").pack(padx=4, side='right')

        self.rnc_recorder.record("Command and Responds for RNC")
        self.tester_recorder.record("Command and Responds for tester")
        self.logger.record("Debug information window")

        self.click_bunch('logger')

    def createwidget_msg_text(self, parent):
        frame = TK.Frame(parent)
        text = TK.Text(frame, height=28, width=100)
        bar = TK.Scrollbar(frame)
        text.pack(side='left', fill='y')
        bar.pack(side='right', fill='y')
        bar.config(command=text.yview)
        text.config(yscrollcommand=bar.set, state='disable')
        return frame, text

    def createwidget_separator(self, parent, side, packable=True, **args):
        ''' a separator vertical line'''
        args['bd'] = 1
        args['relief'] = 'sunken'
        if side == 'left' or side == 'right':
            fill = 'y'
            args['width'] = 2
            separator = TK.Frame(parent, **args)
        else:
            fill = 'x'
            args['height'] = 2
            separator = TK.Frame(parent, **args)
        if packable:
            separator.pack(side=side, fill=fill)
        return separator

    def click_bunch(self, key):
        last = self.bunchs['last']
        if last:
            self.bunchs[last][1].config(relief='flat', state='normal')
            self.bunchs[last][0].forget()
        self.bunchs[key][1].config(relief='raised', state='disabled')
        self.bunchs[key][0].pack()
        self.bunchs['last'] = key

    def show_edit_bunch(self):
        if self.info:
            self.show_extra_bunch('edit')
            self.update_info2editinfo()
        else:
            tkMessageBox.showerror("Error", "No RNC selected!")

    def show_add_bunch(self, rnctype):
        self.show_extra_bunch('add')
        self.update_default_addinfo(rnctype)

    def show_extra_bunch(self, key):
        self.bunchs[key][-1].pack(side='left', fill='y')
        self.bunchs[key][-2].pack(side='left')
        self.click_bunch(key)

    def close_extra_bunch(self, key):
        self.click_bunch('logger')
        self.bunchs[key][1].forget()
        self.bunchs[key][2].forget()

    def about_box(self):
        filename = os.path.basename(__file__)
        processname = '.'.join(filename.split('.')[:-1])
        ver_info = "%s\nVersion: %s\nUpdate: %s\nAuthor: %s\n" % \
            (processname, __version__, __date__, __author__)
        tkMessageBox._show("About", ver_info, None, 'ok')
        pass

    def load_default(self):
        set_logger_level(self.defcfg.get('loglevel'))

        default_rnc = self.defcfg.get('rnc')
        #self.defcfg.set('rnc', '')
        self.select_rnc(default_rnc, True)

    def update_interface(self):
        # release rnc and tester
        self.rnc = None
        self.tester = None
        self.update_menu()
        if self.info:
            options = {}
            options['logger'] = self.logger

            options['name'] = self.info.name
            options['info'] = self.info.rnc
            options['recorder'] = self.rnc_recorder
            if self.info.type == "cRNC":
                self.rnc = crnc.Crnc(**options)
            else:
                self.rnc = mcrnc_ada.McrncAda(**options)

            options['name'] = "Tester"
            options['info'] = self.info.tester
            options['recorder'] = self.tester_recorder
            options['ets'] = self.info.ets
            self.tester = tester.Tester(**options)

            self.widgets['name'].set(self.info.name)
            info = "%s[%s %s/%s] Tester[%s %s/%s] ets[%s]" % (self.info.name, 
                   str(self.info.rnc.hostname),
                   str(self.info.rnc.username),
                   str(self.info.rnc.password),
                   str(self.info.tester.hostname),
                   str(self.info.tester.username),
                   str(self.info.tester.password),
                   str(self.info.ets))
            self.widgets['detail'].set(info)
        else:
            self.widgets['name'].set("[NO RNC SELECT]")
            self.widgets['detail'].set("")

    def add_counter_frame(self, parent):
        new_frame = TK.Frame(parent)
        TK.Button(new_frame, text="Action", width=8
            ).pack(padx=6, pady=4)
        self.createwidget_separator(new_frame, 'bottom', True, width=821) 
        return new_frame

    def update_info2editinfo(self):
        self.editinfo['title'].set("Edit %s information" % self.info.name)
        self.editinfo['name'].set(self.info.name)
        self.editinfo['type'].set(self.info.type)
        self.editinfo['rnc_hostname'].set(self.info.rnc.hostname)
        self.editinfo['rnc_username'].set(self.info.rnc.username)
        self.editinfo['rnc_password'].set(self.info.rnc.password)
        self.editinfo['tester_hostname'].set(self.info.tester.hostname)
        self.editinfo['tester_username'].set(self.info.tester.username)
        self.editinfo['tester_password'].set(self.info.tester.password)
        self.editinfo['ets'].set(self.info.ets)

    def update_default_addinfo(self, rnctype):
        self.addinfo['title'].set("Add a new %s" % rnctype)
        self.addinfo['name'].set(rnctype+'_')
        self.addinfo['type'].set(rnctype)
        if rnctype == 'cRNC':
            rnc_username = "SYSTEM"
            rnc_password = "SYSTEM"
        else:
            rnc_username = "root"
            rnc_password = "root"
        self.addinfo['rnc_hostname'].set('')
        self.addinfo['rnc_username'].set(rnc_username)
        self.addinfo['rnc_password'].set(rnc_password)
        self.addinfo['tester_hostname'].set('')
        self.addinfo['tester_username'].set("guest")
        self.addinfo['tester_password'].set("guest1")

    def add_editinfo(self, parent, text, var, row, col, width=20, colspan=1, 
                     readonly=False):
        TK.Label(parent, text=text, font=self.font
            ).grid(row=row, column=col*2, sticky='e', 
                   padx=2 if col==1 else 0)

        TK.Entry(parent, bg="#FFFFFF", font=self.font, width=width, 
                 state="readonly" if readonly else "normal",
                 textvariable=var
            ).grid(row=row, column=col*2+1, columnspan=colspan, 
                   padx=2 if col==0 else 0)


    def editable_frame(self, parent, info, framename):
        row = 0
        self.add_editinfo(parent, "RNC:", info['name'], row, 0, 
                          readonly=False if framename == 'add' else True)
        self.add_editinfo(parent, "Type:", info['type'], row, 1, readonly=True)
        row += 1
        self.add_editinfo(parent, "RNC IP:", info['rnc_hostname'], row, 0)
        self.add_editinfo(parent, "Tester IP:", info['tester_hostname'], row, 1)
        row += 1
        self.add_editinfo(parent, "username:", info['rnc_username'], row, 0)
        self.add_editinfo(parent, "username:", info['tester_username'], row, 1)
        row += 1
        self.add_editinfo(parent, "password:", info['rnc_password'], row, 0)
        self.add_editinfo(parent, "password:", info['tester_password'], row, 1)
        row += 1
        self.add_editinfo(parent, "ets:", info['ets'], row, 0, 53, 3)
        return row

    def add_editframe(self, parent, info, framename):
        edit_frame = TK.Frame(parent)
        self.createwidget_separator(edit_frame, 'top', True, width=821)
        tmpfont = tkFont.Font(size=12, family='Segoe UI', weight='bold')
        TK.Label(edit_frame, font=tmpfont, textvariable=info['title']
            ).pack(fill='x', pady=8)
        info_frame = TK.Frame(edit_frame)
        info_frame.pack()

        row = self.editable_frame(info_frame, info, framename)
        row += 1

        button_frame = TK.Frame(info_frame)
        button_frame.grid(row=row, column=2, columnspan=2, sticky='e')
        TK.Button(button_frame, text="OK", width=8, font=self.font, 
                  command=lambda: self.check_editframe(info, framename)
            ).pack(side='right', padx=2, pady=4)
        TK.Button(button_frame, text="Cancel", width=8, font=self.font, 
                  command=lambda: self.close_extra_bunch(framename)
            ).pack(side='right', padx=10, pady=4)
        if framename == 'edit':
            tmpFont = tkFont.Font(size=8, family='Segoe UI')
            TK.Button(info_frame, text="Delete", width=6, font=tmpFont, 
                      height=1, command=self.check_delinfo
                  ).grid(row=row, column=1, sticky='w', padx=2)
        self.createwidget_separator(edit_frame, 'bottom', True, width=821) 
        return edit_frame

    def compare_info(self, info):
        if info['rnc_hostname'].get() != self.info.rnc.hostname:
            return False
        if info['rnc_username'].get() != self.info.rnc.username:
            return False
        if info['rnc_password'].get() != self.info.rnc.password:
            return False
        if info['tester_hostname'].get() != self.info.tester.hostname:
            return False
        if info['tester_username'].get() != self.info.tester.username:
            return False
        if info['tester_password'].get() != self.info.tester.password:
            return False
        if info['ets'].get() != self.info.ets:
            return False
        return True

    def update_info2yamel(self, info):
        if not self.info:
            self.info = config.TestEnvInfo()
        self.info.name = info['name'].get()
        self.info.type = info['type'].get()
        self.info.ets = info['ets'].get()
        self.info.rnc.hostname = info['rnc_hostname'].get()
        self.info.rnc.username = info['rnc_username'].get()
        self.info.rnc.password = info['rnc_password'].get()
        self.info.tester.hostname = info['tester_hostname'].get()
        self.info.tester.username = info['tester_username'].get()
        self.info.tester.password = info['tester_password'].get()
        self.listcfg.save(self.info)
        self.update_interface()

    def check_editframe(self, info, framename):
        if self.is_locked() or not self.is_taskable():
            return None
        if framename == 'add':  # add a new rnc
            rnc_name = info['name'].get()
            rnc_type = info['type'].get()
            splits = rnc_name.split('_')
            if len(splits) < 2 or splits[0] != rnc_type or \
                not re.match(r"^[0-9]+A?$", splits[1]):
                msg = "Error name: " + rnc_name + "\n" + \
                      "RNC name must the same as " + \
                      "{0}_12 or {0}_12A!".format(rnc_type) 
                tkMessageBox.showerror("Add info error", msg)
                return None
            elif self.listcfg.has_rnc(rnc_name):
                msg = "This RNC %s is already exist." % rnc_name
                tkMessageBox.showerror("Add info error", msg)
                return None
            else:
                self.logger.info("Add information for %s successful." % rnc_name)
                self.update_info2yamel(info)
                self.update_rnc_list(self.info.type)
                self.select_rnc(self.info.name)
        else:
            if not self.compare_info(info):
                self.logger.info("Update information for %s successful." %  self.info.name)
                self.update_info2yamel(info)
            else:
                self.logger.warn("No information changed for %s." %  self.info.name)
        self.close_extra_bunch(framename)

    def check_delinfo(self):
        if self.is_locked() or not self.is_taskable():
            return None
        msg = "Are you sure to Delete this %s information?" % self.info.name
        if tkMessageBox.askquestion("Question", msg) == "yes":
            self.logger.warn("Delete information for %s successful." %  self.info.name)
            self.listcfg.remove_rnc(self.info.name)
            self.update_rnc_list(self.info.type)
            self.info = None
            self.defcfg.set('rnc', '')
            self.update_interface()
            self.close_extra_bunch('edit')

    def is_locked(self):
        if not self.blinds['collect'][1]:
            msg = "RNC locked due to catch log started!\n" + \
                "Please stop the catch log task firstly."
            tkMessageBox.showerror("Error", msg)
            return True
        else:
            return False

    def is_taskable(self):
        if self.singal.isSet():
            tkMessageBox.showerror("Error", "There's a task on doing!")
            return False
        else:
            return True


    def set_emil_unit(self, units):
        '''set crnc unit for catch emil
        if locked, means that catch emil log already stared, 
        and recovery set.'''

        if self.is_locked():
            emil_units = self.rnc.get_emil_units()
            for key, var in emil_units.items():
                units[key].set(1 if var else 0)
        else:
            emil_units = {}
            for key, var in units.items():
                emil_units[key] = var.get() == 1
            self.rnc.set_emil_units(emil_units)

    def set_task(self, task, **kwargs):
        if self.is_taskable():
            if kwargs.has_key('check_lock') and kwargs.pop('check_lock') and self.is_locked():
                return None
            self.task = task
            self.task_kwargs = kwargs
            self.singal.set()

    def set_task_pre(self, *args):
        if not self.is_taskable():
            return None
        if args[0] == "restart":
            title = "Restart " + args[1]
            msg = "Are you sure to restart {0}?".format(
                    self.info.name if args[1] == "rnc" else args[1])
            if tkMessageBox.askquestion(title, msg) == "yes":
                self.set_task(self.task_restart, target=args[1], check_lock=True)
        elif args[0] == "plan":
            if self.widgets['plan'].get() == 1:
                options = {}
                options['defaultextension'] = ".xml"
                options['filetypes'] = [("xml files", ".xml")]
                options['parent'] = self.root
                options['title'] = "Chose a plan file"
                defalt_plan_path = self.defcfg.get('plan_path')
                if defalt_plan_path and os.path.isdir(defalt_plan_path):
                    options['initialdir'] = defalt_plan_path
                xml = tkFileDialog.askopenfilename(**options)
                if xml:
                    new_plan_path = os.path.dirname(xml)
                    if defalt_plan_path != new_plan_path:
                        self.defcfg.set('plan_path', new_plan_path)
                    self.set_task(self.task_plan_active, xml=xml)

            else:
                title = "Delete whole plan"
                msg = "Are you sure to delete the whole plan in %s?" % \
                        self.info.name
                if tkMessageBox.askquestion(title, msg) == "yes":
                    self.set_task(self.task_plan_delete)
        elif args[0] == "format":
            options = {}
            options['defaultextension'] = ".xml"
            options['filetypes'] = [("xml files", ".xml")]
            options['parent'] = self.root
            options['title'] = "Chose a xml file for formatting"
            defalt_path = self.defcfg.get('format_path')
            if defalt_path and os.path.isdir(defalt_path):
                options['initialdir'] = defalt_path
            xml = tkFileDialog.askopenfilename(**options)
            if xml:
                new_path = os.path.dirname(xml)
                if defalt_path != new_path:
                    self.defcfg.set('format_path', new_path)
                self.set_task(self.task_format_xml, xml=xml)

    def task_check(self, unit):
        if unit == "rnc":
            check_func = self.rnc.check
        elif unit == "ets":
            check_func = self.tester.check_ets
        elif unit == "wireshark":
            check_func = self.tester.check_wireshark
        else:
            self.logger.error("Check function input error.")
            return

        check_func(force=True)


    def task_disconnect(self):
        if self.rnc.connected:
            self.rnc.disconnect()
        if self.tester.connected:
            self.tester.close()
        self.logger.warn("Disconnect RNC and Tester for %s." % self.info.name)

    def task_restart(self, target):
        if target == "rnc":
            self.rnc.restart()
        elif target == "tester":
            self.tester.restart()

    def task_format_xml(self, xml):
        self.logger.notice("Format xml file: " + xml, end=' ')
        if xml_tool.tidy_xml(xml):
            self.logger.notice("successful", tag='g')
        else:
            self.logger.notice("failed", tag='r')

    def task_catch_start(self):
        task_rslt = False
        blind_collect = False
        for obj in sorted(self.objects):
            if 'catch' not in obj[-1]:
                continue
            # if not choice, pass
            if not self.widgets['catch'][obj[1]].get():
                continue
            #if this task started(False), pass
            if not self.blinds[obj[1]][1]:
                blind_collect = True
                continue

            self.blinds[obj[1]][0].config(state='disabled')
            if self.task_do(obj[3], 'catch', obj[1]):
                task_rslt = task_rslt or True
                self.blinds[obj[1]][1] = False
                blind_collect = True
            else:
                self.blinds[obj[1]][0].config(state='normal')
                task_rslt = task_rslt or False

        if self.blinds['collect'][1] and blind_collect:
            self.blinds['collect'][0].config(state='disabled')
            self.blinds['collect'][1] = False

        if not task_rslt:
            self.logger.warn("Task for start catch log exception and stopped.")

    def task_catch_stop(self):
        #self.task_notice("stop catch logs")
        task_rslt = False

        for obj in sorted(self.objects, reverse=True):
            if 'catch' not in obj[-1]:
                continue
            if not self.widgets['catch'][obj[1]].get():
                continue
            self.task_do(obj[3], 'catch', obj[1], ex="stop")
            self.blinds[obj[1]][1] = True
            self.blinds[obj[1]][0].config(state='normal')
            task_rslt = True

        if not self.blinds['collect'][1]:
            self.blinds['collect'][0].config(state='normal')
            self.blinds['collect'][1] = True

        if not task_rslt:
            self.logger.warn("Task for stop catch log exception and stopped.")


    def task_collect(self):
        t = time.strftime('%Y%m%d_%H%M%S', time.localtime())
        logpath = "{0}\\{1}\\{1}_{2}".format(LOG_PATH, self.info.name, t)
        casename = self.widgets['case'].get()
        if casename:
            logpath = "%s_%s" % (logpath, casename)

        task_rslt = False
        for obj in self.objects:
            if 'collect' not in obj[-1]:
                continue
            # if not choice, pass
            if not self.widgets['collect'][obj[1]].get():
                continue
            task_rslt = self.task_do(obj[3], 'collect', obj[1], path=logpath) \
                or task_rslt

        if not task_rslt:
            self.logger.warn("Task for collect log exception and stopped.")


    def task_do(self, obj, operate, name, ex=None, path=None):
        """
        @obj: rnc|tester
        @operate: catch|collect
        @name: emil|counter|wireshark|ets|plan
        @ex: stop|start for catch
        @path: log path for collect
        """
        attr = "%s_%s" % (operate, name)
        if ex:
            attr += "_%s" % ex
        hand_obj = getattr(self, obj)
        if hasattr(hand_obj, attr):
            func = getattr(hand_obj, attr)
            if path:
                return func(path)
            else:
                return func()
        else:
            return False


    def task_plan_active(self, xml):
        self.rnc.download_active_plan(xml, self.plan_trace.get())

    def task_plan_delete(self):
        deletexml = xml_tool.make_del_xml(self.rnc.collect_plan())

        if self.info.type == "cRNC":
            sleep_time = 60.0
        else:
            sleep_time = 15.0

        if deletexml:
            self.rnc.log('info', "delete plan created OK. Please wait %s seconds." % sleep_time)
            retry = 2
            times = 0
            while times < retry:
                if times != 0:
                    self.rnc.log('warn', "After %s seconds will retry %d." % (sleep_time, times))
                times += 1
                time.sleep(sleep_time)
                if self.rnc.download_active_plan(deletexml, True):
                    break
        else:
            self.rnc.log('error', "delete plan created error.")


    def monitor(self):
        """background monitor"""
        time.sleep(0.5)
        self.logger.record("Background monitor  started.")

        while True:
            self.singal.wait()
            if self.task:
                self.task(**self.task_kwargs)
                self.task = None
                self.task_kwargs = None
            self.singal.clear()

    def watchdog(self):
        """watchdog for keep rnc and tester connect
        every 1 minute to check how long rnc and tester are inactive,
        if it's more than 10 minutes, send a 'Enter' command to keep connect
        """
        time.sleep(1.0)
        self.logger.record("Connection watchdog started.\n")

        class Stamp:
            def __init__(self, interval, maxtimes, times, timestamp):
                self.interval = interval
                self.maxtimes = maxtimes
                self.times = times
                self.timestamp = timestamp

        stamp_rnc = Stamp(180, 3, 0, 0)
        stamp_tester = Stamp(600, 2, 0, 0)

        while True:
            time.sleep(60.0)
            if self.singal.isSet():
                continue
            t = int(time.time())
            if self.info:
                if self.info.type == "cRNC":
                    stamp_rnc.interval = 180
                    stamp_rnc.maxtimes = 3
                else:
                    stamp_rnc.interval = 600
                    stamp_rnc.maxtimes = 2
            self.send_blank(t, self.rnc, stamp_rnc)
            self.send_blank(t, self.tester, stamp_tester)

    def send_blank(self, t, obj, stamp):
        if obj and obj.connected:
            send_blank = False
            if t - obj.get_timestamp() > stamp.interval:
                send_blank = True
                stamp.times += 1
            elif stamp.timestamp != obj.get_timestamp():
                stamp.times = 0

            if stamp.times > stamp.maxtimes:
                obj.log('notice', "watchdog: connect overtime and disconnect.\n")
                obj.close()
            elif send_blank:
                obj.send('', revert=False)
                stamp.timestamp = obj.get_timestamp()

def prepstr(s):
    # Helper to extract the underscore from a string, e.g.
    # prepstr("Co_py") returns (2, "Copy").
    i = s.find('_')
    if i >= 0:
        s = s[:i] + s[i+1:]
    return i, s

def init_logger():
    process_num = 0
    logger = logging.getLogger('log')
    log_backed = False
    
    
    filename = os.path.basename(__file__)
    processname = '.'.join(filename.split('.')[:-1])

    for p in list(psutil.process_iter()):
        if processname in str(p):
            process_num += 1
    
    log_path = os.path.join(os.getcwd(), "log")
    if not os.path.isdir(log_path):
        os.makedirs(log_path)
    if process_num > 1:
        log_name = os.path.join(log_path, "logger_%d.txt" % process_num)
    else:
        log_name = os.path.join(log_path, "logger.txt")
    plan_log_name = os.path.join(log_path, "rnwplan_logger.txt")
    
    for name in (log_name, plan_log_name):
        if os.path.isfile(name):
            file_size = os.path.getsize(name)
            if file_size > 1024000: # 1M bytes
                log_file_bak = name + ".bak"
                if os.path.isfile(log_file_bak):
                    os.remove(log_file_bak)
                os.rename(name, log_file_bak)
                if name == log_name:
                    log_backed = True

    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    fh = logging.FileHandler(log_name)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    if log_backed:
        logger.warning("log %s is too bigger, and renamed" % log_name)

    plan_logger = logging.getLogger('rnw_plan')
    formatter = logging.Formatter("%(asctime)s - %(message)s")
    fh2 = logging.FileHandler(plan_log_name)
    fh2.setFormatter(formatter)
    plan_logger.addHandler(fh2)
    plan_logger.setLevel(logging.INFO)

def set_logger_level(level):
    if hasattr(logging, level.upper()):
        logger = logging.getLogger('log')
        logger.setLevel(getattr(logging, level.upper()))

if __name__ == '__main__':
    init_logger()
    root = TK.Tk(className=" Manual Collector")
    ManualCollector(root)
    root.mainloop()
